#include "mem_hash.h"
#include "mem_file.h"
#include "mem_buffer.h"

#include <stdint.h>

size_t current_mem_size;
size_t max_mem_size;
off_t current_swap_size;
off_t max_swap_size;

size_t id_count;

bool initializated = false;

int ma_init(size_t mem, off_t swap, const char* swap_path) {
     if (mem > swap) {
	  errno = EBADR;
	  return 0;
     }
     
     if (initializated) {
	  errno = EALREADY;
	  return 0;
     }
     
     if (buffer_create(mem) == 0) {
	  errno = ENOMEM;
	  return 0;
     }
     
     if (file_create(swap, swap_path) == 0) return 0;
     
     hash_create_table();
     
     initializated = true;
     
     current_mem_size = 0;
     current_swap_size = 0;
     max_mem_size = mem;
     max_swap_size = swap;
     id_count = 0;
     
     return 1;
}

void ma_deinit() {
     if (initializated) {
	  
	  file_remove();
	  buffer_remove();
	  hash_remove_table();
	  
	  initializated = false;
     }
}

size_t ma_alloc(size_t sz) {
     if (!initializated) {
	  errno = ECANCELED;
	  return 0;
     }
     
     if (max_mem_size < sz) {
	  errno = E2BIG;
	  return 0;
     }
     
     if (max_swap_size - current_swap_size < sz) {
	  errno = ENOMEM;
	  return 0;
     }
     
     if (id_count == SIZE_MAX) {
	  errno = EMLINK;
	  return 0;
     }
     
     id_count++;
     hash_add(id_count, sz);
     
     return id_count;
}

int ma_free(size_t id) {
     if (!initializated) {
	  errno = ECANCELED;
	  return 0;
     }
     
     hash_element_t *info = hash_find(id);
     
     if (info == NULL) {
	  errno = EFAULT;
	  return 0;
     }
     
     if (info->get_count > 0) {
	  errno = EBUSY;
	  return 0;
     }
     
     if (info->swap_addr != -1) {
	  file_delete(info->swap_addr);
     }
     
     hash_delete(id);
     return 1;
}

void *ma_get(size_t id) {
     if (!initializated) {
	  errno = ECANCELED;
	  return NULL;
     }
     
     hash_element_t *info = hash_find(id);
     
     if (info == NULL) {
	  errno = EFAULT;
	  return NULL;
     }
     
     if (current_mem_size + info->sz > max_mem_size) {
	  errno = ENOMEM;
	  return NULL;
     }
     
     if (info->get_count > 0) {
	  info->get_count++;
	  return info->mem_addr;
     }
     
     void *value = NULL;
     if (info->swap_addr != -1) {
	  value = malloc(info->sz);
	  file_read(value, info->swap_addr, info->sz);
     }
     
     info->mem_addr = buffer_write(value, info->sz);
     
     if (info->swap_addr != -1) free(value);
     
     if (info->mem_addr == NULL) {
	  errno = ENOBUFS;
	  return NULL;
     }
     
     info->get_count++;
     return info->mem_addr;
}

int ma_release(size_t id) {
     if (!initializated) {
	  errno = ECANCELED;
	  return 0;
     }
     
     hash_element_t *info = hash_find(id);
     
     if (info == NULL) {
	  errno = EFAULT;
	  return 0;
     }
     
     if (info->get_count == 0) {
	  errno = EFAULT;
	  return 0;
     }
     
     if (info->get_count > 1) {
	  info->get_count--;
	  return 1;
     }
     
     if (info->swap_addr == -1) {
	  info->swap_addr = file_write_without_defragmentation(info->mem_addr, info->sz);
	  if (info->swap_addr == -1) info->swap_addr = file_write_with_defragmentation(info->mem_addr, info->sz);
     }
     
     buffer_delete(info->mem_addr);
     info->mem_addr = NULL;
     
     info->get_count--;
     return 1;
}
